/**
 * PHOSIDE: PHosphorylation Site IDentification Engine.
 * Copyright 2009 Digital Biology Lab, University of Missouri.
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 3 of the License, or (at your option) any later
 * version. <p/> This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the License for more
 * details.
 */

package phoside.ui;

import java.io.File;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;

import javax.swing.event.TreeExpansionEvent;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.event.TreeWillExpandListener;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.table.DefaultTableModel;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

import phoside.Phoside;
import phoside.PhosideInit;

import phoside.model.PhosideModel;
import phoside.model.PhosideModelImpl;
import phoside.model.PhosideModelManager;

import phoside.ui.task.ModelLoadingTask;
import phoside.ui.task.ModelSavingTask;
import phoside.ui.task.TaskUtil;
import phoside.ui.util.checktree.CheckTreeManager;
import phoside.ui.util.checktree.TreePathSelectable;

import phoside.util.FileExtensionsFilter;
import phoside.util.IOUtil;
import phoside.util.StaticFinalValues;

/**
 *
 * @author gjj
 */
public class ModelManageFrame extends javax.swing.JFrame {

    /** Creates new form ModelManageFrame */
    public ModelManageFrame() {
        initComponents();
        viewModelTable = new ViewModelTable(null);
        viewFileText = new JTextArea();
        viewFileText.setEditable(false);
        viewFileText.setLineWrap(true);
        viewFileText.setWrapStyleWord(true);
        setDefaultCloseOperation(javax.swing.JFrame.DISPOSE_ON_CLOSE);
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        java.awt.GridBagConstraints gridBagConstraints;

        javax.swing.JSplitPane modelManageSplitPane = new javax.swing.JSplitPane();
        javax.swing.JPanel modelManagePanel = new javax.swing.JPanel();
        javax.swing.JScrollPane modelManagerScrollPane = new javax.swing.JScrollPane();
        javax.swing.JPanel operationPanel = new javax.swing.JPanel();
        javax.swing.JButton removeButton = new javax.swing.JButton();
        javax.swing.JButton combineButton = new javax.swing.JButton();
        javax.swing.JPanel viewModelPanel = new javax.swing.JPanel();
        viewModelScrollPane = new javax.swing.JScrollPane();
        modelCommentPanel = new javax.swing.JPanel();
        javax.swing.JScrollPane modelCommentScrollPane = new javax.swing.JScrollPane();
        modelCommentTextArea = new javax.swing.JTextArea();
        javax.swing.JPanel saveCommentPanel = new javax.swing.JPanel();
        javax.swing.JButton saveCommentButton = new javax.swing.JButton();
        javax.swing.JPanel OKPanel = new javax.swing.JPanel();
        javax.swing.JButton closelBtn = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("Model Management");
        getContentPane().setLayout(new java.awt.GridBagLayout());

        modelManagePanel.setLayout(new java.awt.GridBagLayout());

        modelManagerScrollPane.setMinimumSize(new java.awt.Dimension(300, 500));
        modelManagerScrollPane.setPreferredSize(new java.awt.Dimension(300, 500));

        modelManageTree = new ModelManageTree();
        modelManagerScrollPane.setViewportView(modelManageTree);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        modelManagePanel.add(modelManagerScrollPane, gridBagConstraints);

        removeButton.setText("Remove selected models");
        removeButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                removeButtonActionPerformed(evt);
            }
        });
        operationPanel.add(removeButton);

        combineButton.setText("Combine the selected models");
        combineButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                combineButtonActionPerformed(evt);
            }
        });
        operationPanel.add(combineButton);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.HORIZONTAL;
        modelManagePanel.add(operationPanel, gridBagConstraints);

        modelManageSplitPane.setLeftComponent(modelManagePanel);

        viewModelPanel.setPreferredSize(new java.awt.Dimension(600, 600));
        viewModelPanel.setLayout(new java.awt.GridBagLayout());

        viewModelScrollPane.setMinimumSize(new java.awt.Dimension(400, 400));

        viewModelTable = new ViewModelTable(null);
        viewModelScrollPane.setViewportView(viewModelTable);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        viewModelPanel.add(viewModelScrollPane, gridBagConstraints);

        modelCommentPanel.setVisible(false);
        modelCommentPanel.setLayout(new java.awt.GridBagLayout());

        modelCommentScrollPane.setViewportBorder(javax.swing.BorderFactory.createTitledBorder("Comments"));

        modelCommentTextArea.setLineWrap(true);
        modelCommentTextArea.setRows(10);
        modelCommentTextArea.setWrapStyleWord(true);
        modelCommentScrollPane.setViewportView(modelCommentTextArea);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        modelCommentPanel.add(modelCommentScrollPane, gridBagConstraints);

        saveCommentButton.setText("Save comment");
        saveCommentButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                saveCommentButtonActionPerformed(evt);
            }
        });
        saveCommentPanel.add(saveCommentButton);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_END;
        modelCommentPanel.add(saveCommentPanel, gridBagConstraints);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 1;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 2.0;
        viewModelPanel.add(modelCommentPanel, gridBagConstraints);

        modelManageSplitPane.setRightComponent(viewModelPanel);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 0;
        gridBagConstraints.fill = java.awt.GridBagConstraints.BOTH;
        gridBagConstraints.weightx = 1.0;
        gridBagConstraints.weighty = 1.0;
        getContentPane().add(modelManageSplitPane, gridBagConstraints);

        closelBtn.setText("Close");
        closelBtn.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                closelBtnActionPerformed(evt);
            }
        });
        OKPanel.add(closelBtn);

        gridBagConstraints = new java.awt.GridBagConstraints();
        gridBagConstraints.gridx = 0;
        gridBagConstraints.gridy = 4;
        gridBagConstraints.anchor = java.awt.GridBagConstraints.LINE_END;
        gridBagConstraints.insets = new java.awt.Insets(5, 5, 5, 5);
        getContentPane().add(OKPanel, gridBagConstraints);

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void closelBtnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closelBtnActionPerformed
        setVisible(false);
        this.dispose();
}//GEN-LAST:event_closelBtnActionPerformed

    private void saveCommentButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_saveCommentButtonActionPerformed
        PhosideModel model = modelManageTree.getSelectedModel();
        if (model!=null) {
            model.setComment(modelCommentTextArea.getText());

            String file = model.getFile();

            ModelSavingTask task = new ModelSavingTask(null, file, "gz");

            TaskUtil.execute(task, this);

            if (task.success()) {
                JOptionPane.showMessageDialog(this,"saved");
            } else {
                JOptionPane.showMessageDialog(this,"failed");
            }
        }
    }//GEN-LAST:event_saveCommentButtonActionPerformed

    private void removeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_removeButtonActionPerformed
        List<PhosideModel> models = modelManageTree.getCheckedModels();
        if (models==null || models.isEmpty()) {
            JOptionPane.showMessageDialog(this, "Please select at least one model.");
            return;
        }

        int op = JOptionPane.showConfirmDialog(this, "Are you sure to remove the selected model(s)?\n" +
                "Note: removing all models from a model file will make that model file removed permanently.",
                "remove?", JOptionPane.YES_NO_OPTION);
        if (op == JOptionPane.NO_OPTION) {
            return;
        }

        PhosideModelManager manager = Phoside.getModelManager();

        Set<String> files = new HashSet();
        int n = models.size();
        for (int i=n-1; i>=0; i--) {
            PhosideModel model = models.get(i);
            String file = model.getFile();
            try {
                manager.removeModelFromFile(file, model);
            } catch (Exception e) {
                e.printStackTrace();
                continue;
            }

            files.add(file);
        }

        for (String file : files) {
            List<PhosideModel> ms;
            try {
                ms = manager.loadModels(file);
            } catch (Exception e) {
                e.printStackTrace();
                continue;
            }

            if (ms.isEmpty()) {
                manager.removeModelFile(file);
                modelManageTree.removeFile(file);
                IOUtil.deleteFile(file);
            } else {
                ModelSavingTask task = new ModelSavingTask(null, file, "gz");
                TaskUtil.execute(task, this);

                if (!task.success()) {
                    JOptionPane.showMessageDialog(this,"failed to update "+file);
                }

                modelManageTree.addFile(file); // reflesh
            }
        }

        modelManageTree.repaint();

        JOptionPane.showMessageDialog(this,"Done.");
    }//GEN-LAST:event_removeButtonActionPerformed

    private void combineButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_combineButtonActionPerformed
        List<PhosideModel> models = modelManageTree.getCheckedModels();

        if (models==null || models.isEmpty()) {
            JOptionPane.showMessageDialog(this, "Please select at least one model.");
            return;
        }

        List<PhosideModel> modelsCopy = new ArrayList(models.size());
        for (PhosideModel model : models) {
            modelsCopy.add(new PhosideModelImpl(model));
        }

        JFileChooser fc = new JFileChooser(PhosideInit.modelDir);

        String ext = StaticFinalValues.MODELAPPEDIX;
        ArrayList<String> exts = new ArrayList<String>(1);
        exts.add(ext);
        fc.setFileFilter(new FileExtensionsFilter(exts,"Model file (."+ext+")"));
        //fc.setAcceptAllFileFilterUsed(true);
        fc.setDialogTitle("Save to...");
        int returnVal = fc.showSaveDialog(this);
        if (returnVal == JFileChooser.APPROVE_OPTION) {
            File file = fc.getSelectedFile();
            //PhosideInit.defaultPath = file.getParent();

            String filePath = file.getAbsolutePath();
            if (!filePath.endsWith("."+ext)) {
                filePath += "."+ext;
            }

            if (IOUtil.fileExist(filePath)) {
                int op = JOptionPane.showConfirmDialog(this,
                        "Are you sure to replace the existing model file?", "Replace?", JOptionPane.YES_NO_CANCEL_OPTION);
                if (op == JOptionPane.NO_OPTION) {
                    combineButtonActionPerformed(evt);
                    return;
                } else if (op == JOptionPane.CANCEL_OPTION) {
                    return;
                }
            }

            ModelSavingTask task = new ModelSavingTask(modelsCopy, filePath, "gz");

            TaskUtil.execute(task, this);

            if (task.success()) {
                try {
                    Phoside.getModelManager().addModelFile(filePath, modelsCopy);
                } catch (Exception e) {
                    e.printStackTrace();
                    JOptionPane.showMessageDialog(this,"Cannot find "+filePath);
                    return;
                }
                modelManageTree.addFile(filePath);
                modelManageTree.repaint();
                JOptionPane.showMessageDialog(this,"saved");
            } else {
                JOptionPane.showMessageDialog(this,"failed");
            }
        }
    }//GEN-LAST:event_combineButtonActionPerformed

    //private PhosideModel model;
    private ModelManageTree modelManageTree;
    private ViewModelTable viewModelTable;
    private JTextArea viewFileText;

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JPanel modelCommentPanel;
    private javax.swing.JTextArea modelCommentTextArea;
    private javax.swing.JScrollPane viewModelScrollPane;
    // End of variables declaration//GEN-END:variables


    private class ViewModelTable extends JTable {
        private final String headerParam = "Parameter";
        private final String headerValue = "Value";

        public ViewModelTable(PhosideModel model) {
            this.setEnabled(false);
            setPhosideModel(model);
        }

        public void setPhosideModel(PhosideModel model) {
            if (model==null) {
                setModel(new DefaultTableModel());
                return;
            }

            Properties props = model.getModelProperties();

            Set<String> propNames = new TreeSet(props.keySet());

            Object[][] data = new Object[propNames.size()+1][];

            data[0] = new Object[]{"Type of Site", model.getSiteTypes()};

//            data[1] = new Object[]{"Area Under ROC Curver",
//                model.getROC()!=null?model.getROC().auc():"This model was not evaluated"};

            int i = 1;
            for (String p : propNames) {
                data[i++] = new Object[]{p, props.getProperty(p)};
            }

            setModel(new DefaultTableModel(data, new String[]{headerParam, headerValue}));
        }
    }

    private class ModelManageTree extends JTree {
        private DefaultMutableTreeNode rootNode;
        private final CheckTreeManager checkTreeManager;
        private DefaultTreeModel tree_Model;
        private boolean modified = false;
        private Map<String,DefaultMutableTreeNode> mapFileNode;

        public ModelManageTree() {
            boolean dig = false;
            checkTreeManager = new CheckTreeManager(this, dig,
                    new TreePathSelectable() {
                public boolean isSelectable(TreePath path) {
                    return path.getPathCount()==3;
                }
            });

            init();

            setRootVisible(false);

            getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
            
//            this.setExpandsSelectedPaths(true);
        }

        public void init() {
            setupTree();
            setListeners();
        }

//        public void reload() {
//            tree_Model.reload();
//        }

        public boolean isModified() {
            return modified;
        }

        private void setupTree() {
            // set up tree
            rootNode = new DefaultMutableTreeNode("Phoside Models");

            tree_Model = new DefaultTreeModel(rootNode);
            setModel(tree_Model);

            mapFileNode = new HashMap();

            PhosideModelManager manager = Phoside.getModelManager();
            for (String file : manager.modelFiles()) {
                addFile(file);
            }

            expandPath(new TreePath(new DefaultMutableTreeNode[]{rootNode}));
        }

        public void addFile(String file) {
            DefaultMutableTreeNode fileNode = mapFileNode.get(file);
            if (fileNode==null) {
                fileNode = new DefaultMutableTreeNode(new ModelFileWrapper(file));
                insertAlphabetically(rootNode, fileNode);
                mapFileNode.put(file, fileNode);
            } else {
                // if the file exist, reflesh the node
                //Phoside.getModelManager().unloadModels(file);
                fileNode.removeAllChildren();
                //this.collapsePath(new TreePath(new DefaultMutableTreeNode[]{rootNode,fileNode}));
            }

            if (Phoside.getModelManager().isModelFileLoaded(file)) {
                List<PhosideModel> models;
                try {
                    models = Phoside.getModelManager().loadModels(file);
                } catch (Exception ex) {
                    ex.printStackTrace();
                    return;
                }

                for (PhosideModel model : models) {
                    DefaultMutableTreeNode modelNode = new DefaultMutableTreeNode(model);
                    insertAlphabetically(fileNode, modelNode);
                }
            } else {
                insertAlphabetically(fileNode, new DefaultMutableTreeNode("dummy"));
            }
        }

        public void removeFile(String file) {
            DefaultMutableTreeNode fileNode = mapFileNode.get(file);
            if (fileNode==null) {
                return;
            }

            fileNode.removeFromParent();
            mapFileNode.remove(file);

            this.tree_Model.reload();
        }

//        public void refleshFile(String file) {
//            Phoside.getModelManager().unloadModel(file);
//
//        }

        private void setListeners() {
            addTreeWillExpandListener(new TreeWillExpandListener() {
                public void treeWillCollapse(TreeExpansionEvent e) {

                }

                public void treeWillExpand(TreeExpansionEvent e) {
                    TreePath path = e.getPath();
                    if (path==null) {
                        //viewModelTable.setPhosideModel(null);
                    } else {
                        Object nodeObj = path.getLastPathComponent();
                        if (nodeObj instanceof DefaultMutableTreeNode) {
                            DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObj;

                            int level = node.getLevel();
                            if (level==1) {
                                ModelFileWrapper mdf = (ModelFileWrapper)node.getUserObject();
                                String file = mdf.file();
                                if (!Phoside.getModelManager().isModelFileLoaded(file)) {
                                    node.removeAllChildren();

                                    ModelLoadingTask task = new ModelLoadingTask(file);
                                    TaskUtil.execute(task, ModelManageFrame.this);
                                    if (!task.success()) {
                                        JOptionPane.showMessageDialog(ModelManageFrame.this, "Failed to load model: "+file);
                                    }

                                    List<PhosideModel> models = task.getModel();
                                    for (PhosideModel model : models) {
                                        DefaultMutableTreeNode modelNode = new DefaultMutableTreeNode(model);
                                        insertAlphabetically(node, modelNode);
                                    }
                                }
                            }
                        }
                    }
                }
            });

            addTreeSelectionListener(new TreeSelectionListener() {
                public void valueChanged(TreeSelectionEvent e) {
                    TreePath path = e.getPath();
                    if (path==null) {
                        //viewModelTable.setPhosideModel(null);
                    } else {
                        Object nodeObj = path.getLastPathComponent();
                        if (nodeObj instanceof DefaultMutableTreeNode) {
                            DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObj;

                            int level = node.getLevel();
                            if (level==1) {
                                Object obj = node.getUserObject();
                                if (!(obj instanceof ModelFileWrapper))
                                    return;
                                ModelFileWrapper mfw = (ModelFileWrapper)obj;
                                viewFileText.setText("Double click on the node to see the trained model(s) in the model file:\n"+mfw.file());
                                viewModelScrollPane.setViewportView(viewFileText);
                                modelCommentPanel.setVisible(false);
                            } else if (node.getLevel()==2) {
                                Object obj = node.getUserObject();
                                if (!(obj instanceof PhosideModel))
                                    return;
                                PhosideModel model = (PhosideModel)obj;
                                viewModelTable.setPhosideModel(model);
                                viewModelScrollPane.setViewportView(viewModelTable);
                                modelCommentPanel.setVisible(true);
                                modelCommentTextArea.setText(model.getComment());
                            } else {
                                viewModelScrollPane.setVisible(false);
                                modelCommentPanel.setVisible(false);
                            }
                        }
                    }
                }
            });
        }

        public PhosideModel getSelectedModel() {
            TreePath path = getSelectionPath();
            Object nodeObj = path.getLastPathComponent();
            if (nodeObj instanceof DefaultMutableTreeNode) {
                DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObj;
                if (node.getLevel()==2) {
                    return (PhosideModel)node.getUserObject();
                }
            }

            return null;
        }

        public List<PhosideModel> getCheckedModels() {
            TreePath[] paths = checkTreeManager.getSelectionModel().getSelectionPaths();
            List<PhosideModel> models = new ArrayList(paths.length);
            for (TreePath path : paths) {
                Object nodeObj = path.getLastPathComponent();
                if (nodeObj instanceof DefaultMutableTreeNode) {
                    DefaultMutableTreeNode node = (DefaultMutableTreeNode)nodeObj;
                    if (node.getLevel()==2) {
                        PhosideModel model = (PhosideModel)node.getUserObject();
                        models.add(model);
                    }
                }
            }

            return models;
        }

        private void insertAlphabetically(DefaultMutableTreeNode parent,
                DefaultMutableTreeNode insertionNode) {
            String insertionValue = insertionNode.getUserObject().toString();
            int n = parent.getChildCount();
            int i=0;
            for (; i<n; i++) {
                DefaultMutableTreeNode child = (DefaultMutableTreeNode) parent.getChildAt(i);
                if (child.getUserObject().toString().compareTo(insertionValue)>0) {
                    break;
                }
            }

            this.tree_Model.insertNodeInto(insertionNode, parent, i);
//            parent.insert(insertionNode, i);
        }

    }
}

